home *** CD-ROM | disk | FTP | other *** search
/ ftp.alaska-software.com / 2014.06.ftp.alaska-software.com.tar / ftp.alaska-software.com / 3pp / mxsetup.old / {app} / Tooltip.prg < prev   
Text File  |  2001-09-14  |  14KB  |  578 lines

  1. //////////////////////////////////////////////////////////////////////
  2. //
  3. //  TOOLTIP.PRG
  4. //
  5. //  Copyright:
  6. //      Maniacc Software - 01/31/2000
  7. //      Modified by jpc  - 02/23/2000 --- Made to handle up to 3 lines
  8. //      Modified by jpc  - 07/11/2000 --- No line limit - use Memo Field
  9. //
  10. //  Contents:
  11. //      Tooltip help system
  12. //
  13. //////////////////////////////////////////////////////////////////////
  14.  
  15. #include "Gra.ch"
  16. #include "Xbp.ch"
  17. #include "Appevent.ch"
  18. #include "Font.ch"
  19. #include "Dbstruct.ch"
  20. #include "Common.ch"
  21.  
  22. #define nMaxLen 40
  23. #define nNoTip    xbeP_User + 12
  24. #define xbeK_CTRL_ALT_E 852037
  25.  
  26. CLASS MxToolHelp FROM Thread
  27.  
  28.     HIDDEN:
  29.     METHOD DisplayToolTip()
  30.     METHOD PaintTheTip()
  31.     METHOD EditTheTip()
  32.  
  33.     EXPORTED:
  34.     VAR producerID
  35.     VAR oLastMotionXBP
  36.     VAR oLastTipXBP
  37.     VAR oBlockedXBP
  38.     VAR nLastTipTime
  39.     VAR aLastMotionPos
  40.     VAR oTip
  41.     VAR lTipIsShown
  42.     VAR nTipSensitivity
  43.     VAR lShowDefault
  44.     VAR ToolFileName
  45.  
  46.     INLINE METHOD init(cToolFileName)
  47.     Default cToolFileName to "MTOOL"
  48.     ::ToolFileName := cToolFileName
  49.     ::nTipSensitivity := .5
  50.     ::thread:init()
  51.     ::producerID := ThreadID()
  52.     RETURN
  53.  
  54.     EXPORTED:
  55.     METHOD execute()
  56.     METHOD atStart()
  57.     METHOD atEnd()
  58.     METHOD showTip()
  59.     METHOD hideTip()
  60.     METHOD editTip()
  61. ENDCLASS
  62.  
  63. METHOD MxToolHelp:atStart()
  64.     ::lTipIsShown       := .F.
  65.     ::oLastMotionXBP    := NIL
  66.     ::aLastMotionPos    := NIL
  67.     ::nLastTipTime      := seconds()
  68.  
  69.     /*
  70.     * The HELP Database and Index are located in the Current Drive &
  71.     * Directory. The Index expression is: PROG_NAME
  72.     */
  73.     if !file(::ToolFileName + ".DBF")
  74.         MakeHelp(::ToolFileName)
  75.         use (::ToolFileName) alias TIP new exclusive
  76.         index on PROG_NAME to (::ToolFileName)
  77.         use
  78.     endif
  79.     use (::ToolFileName) index (::ToolFileName) alias TIP shared
  80. RETURN
  81.  
  82.  
  83. METHOD MxToolHelp:atEnd()
  84.     DbCloseAll()
  85. RETURN
  86.  
  87. METHOD MxToolHelp:Execute(lDefault)
  88.     LOCAL nEvent, mp1:=0, mp2:=0, oXbp:=NIL
  89.     LOCAL nLastMotionTime := 0, lEdit := .F.
  90.  
  91.     if lDefault==NIL
  92.         lDefault := .F.
  93.     endif
  94.     ::lShowDefault := lDefault    
  95.  
  96.     DO WHILE .T.
  97.  
  98.         Sleep( 20 )
  99.  
  100.         nEvent := LastAppEvent(@mp1,@mp2,@oXbp,::producerID)
  101.         IF oXbp:isDerivedFrom("XbpIWindow")
  102.             oXbp := oXbp:setParent()
  103.         ENDIF
  104.  
  105.  
  106.         if nEvent == xbeM_LbClick .or. nEvent == nNoTip
  107.             ::oBlockedXBP := oXbp
  108.             ::hideTip()
  109.             LOOP
  110.         endif
  111.  
  112.         if nEvent == xbeP_Keyboard.and.mp1==xbeK_CTRL_ALT_E
  113.             ::editTip()
  114.             LOOP
  115.         ELSEIF (ValType(::oBlockedXBP)=="O" .AND. oXbp == ::oBlockedXBP )
  116.             LOOP
  117.         ENDIF
  118.  
  119.         do while oXbp:isDerivedFrom("XbpStatic")
  120.             oXbp := oXbp:setParent()
  121.         enddo
  122.  
  123.  
  124.         IF(nEvent == xbeM_Motion)
  125.  
  126.             ::oBlockedXBP := NIL
  127.  
  128.             IF ( ValType( ::oLastMotionXBP) == "O" .AND. oXbp == ::oLastMotionXBP )
  129.  
  130.                 IF( ::aLastMotionPos[1] == mp1[1] .AND. ;
  131.                     ::aLastMotionPos[2] == mp1[2] )
  132.  
  133.                     IF Seconds()-nLastMotionTime>::nTipSensitivity  // .and. SetAppFocus()<>oXbp
  134.                         ::showTip()
  135.                     ENDIF
  136.  
  137.                 ELSE
  138.                     ::aLastMotionPos := AClone(mp1)
  139.                     nLastMotionTime := Seconds()
  140.                 ENDIF
  141.             ELSEIF ( ValType(::oLastTipXBP) == "O" .AND. ;
  142.                 oXbp:setParent() == ::oLastTipXBP:setParent() .AND. ;
  143.                 oXbp != ::oLastTipXBP )
  144.  
  145.                 ::hideTip()
  146.                 ::oLastMotionXBP  := oXbp
  147.                 ::aLastMotionPos  := AClone(mp1)
  148.                 nLastMotionTime := Seconds()
  149.  
  150.                 IF ::nLastTipTime>0 .AND. Seconds()-::nLastTipTime<=0.5 .and. SetAppFocus()<>oXbp
  151.                     ::showTip()
  152.                 ENDIF
  153.  
  154.             ELSE
  155.                 ::oLastMotionXBP  := oXbp
  156.                 ::aLastMotionPos  := AClone(mp1)
  157.                 nLastMotionTime   := Seconds()
  158.                 ::hideTip()
  159.             ENDIF
  160.         ELSEIF nEvent != xbeP_Paint
  161.             IF oXbp==::oLastTipXBP .or. nEvent==xbeM_LbClick .or.nEvent == nNoTip
  162.                 ::oBlockedXBP    := oXbp
  163.             ENDIF
  164.             ::oLastMotionXBP := NIL
  165.             ::hideTip()
  166.         ENDIF
  167.  
  168.     ENDDO
  169.  
  170. RETURN
  171.  
  172. METHOD MxToolHelp:showTip()
  173.     IF(!::lTipIsShown)
  174.         ::DisplayToolTip(::oLastMotionXBP)
  175.         ::oLastTipXBP  := ::oLastMotionXBP
  176.         ::oBlockedXBP := NIL
  177.     ENDIF
  178. RETURN
  179.  
  180. METHOD MxToolHelp:hideTip()
  181.     IF(::lTipIsShown)
  182.         ::oTip:hide()
  183.         ::oTip:destroy()
  184.         ::lTipIsShown := .F.
  185.         ::nLastTipTime := Seconds()
  186.     ENDIF
  187. RETURN
  188.  
  189. METHOD MxToolHelp:DisplayToolTip(oXbpRequestingHint)
  190.     LOCAL cText := ""
  191.     LOCAL cID   := "", cAlias := alias()
  192.     LOCAL aPos, aChildren, oParent, nChild, nChildren
  193.  
  194.     IF (ValType(oXbpRequestingHint:helpLink)=="O" .and. ;
  195.         oXbpRequestingHint:helpLink:isDerivedFrom("ToolHelpLabel"))
  196.         cSeek := oXbpRequestingHint:cargo
  197.         if oXbpRequestingHint:isDerivedFrom("XbpSLE") .and. !oXbpRequestingHint:editable
  198.             cText := "This data is not editable at this time"
  199.         elseif oXbpRequestingHint:isDerivedFrom("XbpRadioButton")
  200.             if oXbpRequestingHint:editbuffer()
  201.                 cText := "Unmark to Deactivate Option"
  202.             else
  203.                 cText := "Mark to Activate Option"
  204.             endif
  205.         elseif oXbpRequestingHint:isDerivedFrom("XbpCheckBox")
  206.             if oXbpRequestingHint:editbuffer()
  207.                 cText := "Uncheck box to Deactive Option"
  208.             else
  209.                 cText := "Check Box to Activate Option"
  210.             endif
  211.         elseif cSeek <> NIL
  212.             go top
  213.             DBSeek(cSeek,.F.)
  214.             if found().and.alltrim(TIP->PROG_NAME)==alltrim(cSeek)
  215.                 cText := Trim( TIP->T_STRING + TIP->T_MEMO )
  216.             else
  217.                 cText := " "
  218.             endif
  219.         else
  220.             cText := " "
  221.         endif
  222.     ELSE
  223.         if ::lShowDefault
  224.             if oXbpRequestingHint:isDerivedFrom("XbpDialog");
  225.                 .or.oXbpRequestingHint:isDerivedFrom("PBMenuBar")
  226.             elseif oXbpRequestingHint:isDerivedFrom("XbpComboBox")
  227.                 cText := "Select from List"
  228.             elseif oXbpRequestingHint:isDerivedFrom("XbpSLE") .and. !oXbpRequestingHint:editable
  229.                 cText := "This data is Automatic"
  230.             elseif oXbpRequestingHint:isDerivedFrom("XbpPushButton")
  231.                 cText := "Click to Perform Function "
  232.             elseif oXbpRequestingHint:isDerivedFrom("MxPushButton");
  233.                 .or.oXbpRequestingHint:setParent():isDerivedFrom("MxPushButton");
  234.                 .or.oXbpRequestingHint:setParent():setParent():isDerivedFrom("MxPushButton")
  235.                 cText := "Click to Perform Function "
  236.              elseif oXbpRequestingHint:isDerivedFrom("XbpRadioButton")
  237.                 if oXbpRequestingHint:editbuffer()
  238.                     cText := "Unmark to Deactivate Option"
  239.                 else
  240.                     cText := "Mark to Activate Option"
  241.                 endif
  242.             elseif oXbpRequestingHint:isDerivedFrom("XbpCheckBox")
  243.                 if oXbpRequestingHint:editbuffer()
  244.                     cText := "Uncheck box to Deactive Option"
  245.                 else
  246.                     cText := "Check Box to Activate Option"
  247.                 endif
  248.             else
  249.                 cText := " "
  250.             endif
  251.         else
  252.             cText := " "
  253.         endif
  254.     ENDIF
  255.  
  256.  
  257.     /*
  258.     * Ok, now lets paint the TIP
  259.     */
  260.     if !empty(cText)
  261.         aPos  := calcAbsolutePosition(::aLastMotionPos,oXbpRequestingHint)
  262.         aPos[1] += 20
  263.         aPos[2] -= 15
  264.  
  265.         ::oTip := XbpStatic():new()
  266.         ::oTip:options := XBPSTATIC_TYPE_FGNDFRAME
  267.         ::oTip:create(AppDesktop(),AppDesktop(), aPos, { 0 , 0 })
  268.         ::PaintTheTip(cText)
  269.         ::oTip:show()
  270.         ::lTipIsShown := .T.
  271.     else
  272.         ::lTipIsShown := .F.
  273.     endif
  274.  
  275. RETURN(SELF)
  276.  
  277.  
  278. METHOD MxToolHelp:PaintTheTip(cText)
  279.     LOCAL aAttr, oPS, oFont
  280.     LOCAL aPoints, nColor := 16, i, nHeight
  281.     LOCAL aSize := {0,0}, aPos := AppDesktop():currentSize()
  282.     LOCAL aOldPos := ::oTip:currentPos()
  283.  
  284.     oPS := ::oTip:lockPS()
  285.  
  286.     aText := Wrap(cText,nMaxLen)
  287.  
  288.     oFont := XbpFont():new(oPS):create("9.Arial")
  289.     GraSetFont( oPS,oFont )
  290.  
  291.     for i:=1 to len(aText)
  292.         aPoints := GraQueryTextBox( oPS, alltrim(aText[i]) )
  293.         if ((aPoints[3,1]-aPoints[1,1])+8)>aSize[1]
  294.             aSize[1] := (aPoints[3,1] - aPoints[1,1]) + 8
  295.         endif
  296.         if i==1        
  297.             aSize[2] := (aPoints[1,2] - aPoints[2,2]) + 4
  298.             nHeight := aSize[2]-4
  299.         else
  300.             aSize[2] := aSize[2]+(aPoints[1,2]-aPoints[2,2])
  301.         endif
  302.     next i
  303.     ::oTip:unlockPS()
  304.  
  305.     ::oTip:setSize(aSize,.F.)
  306.  
  307.     if aOldPos[1]+aSize[1]>aPos[1]-5
  308.         aPos[1] := aPos[1]-aSize[1]-5
  309.     else
  310.         aPos[1] := aOldPos[1]
  311.     endif
  312.     if aPos[1]+aSize[1] >AppDeskTop():currentSize()[1]
  313.         aPos[1] := AppDeskTop():CurrentSize()[1] - 5 - aSize[1]
  314.     endif
  315.  
  316.     if len(aText)>1
  317.         for i:=2 to len(aText)
  318.             aOldPos[2] := aOldPos[2]-nHeight
  319.         next i
  320.     endif
  321.     if aOldPos[2]<5
  322.         aPos[2] := 5
  323.     else
  324.         aPos[2] := aOldPos[2]
  325.     endif
  326.     if aPos[2] < 6
  327.         aPos[2] := 6
  328.     endif
  329.  
  330.     ::oTip:setPos(aPos,.F.)
  331.  
  332.     oPS := ::oTip:lockPS()
  333.     GraSetFont( oPS,oFont )
  334.     oPS:setColorIndex(16,{255,255,210})
  335.  
  336.     aAttr := Array( GRA_AA_COUNT )
  337.     aAttr [ GRA_AA_COLOR ] := nColor
  338.     GraSetAttrArea( oPS, aAttr )
  339.  
  340.     GraBox( oPS, { 0, 0}, {aSize[1]-1, aSize[2]-1}, GRA_OUTLINEFILL, 6, 6 )
  341.  
  342.     aPos := {4,aSize[2]-nHeight}
  343.     for i:=1 to len(aText)
  344.         GraStringAt( oPS, aPos, aText[i] )
  345.         aPos[2] := aPos[2]-nHeight
  346.     next i
  347.  
  348.     ::oTip:unLockPS( oPS)
  349.  
  350. RETURN(SELF)
  351.  
  352.  
  353. METHOD MxToolHelp:editTip()
  354.     ::EditTheTip(::oLastMotionXBP)
  355. RETURN
  356.  
  357. METHOD MxToolHelp:EditTheTip(oXbpRequestingHint)
  358.     LOCAL cText := ""
  359.     LOCAL oFocus
  360.     LOCAL aPos, aChildren, oParent, nChild, nChildren
  361.     LOCAL cAlias := alias()
  362.  
  363.     if ::oLastMotionXBP==NIL
  364.         return
  365.     endif
  366.  
  367.     IF (ValType(oXbpRequestingHint:helpLink)=="O" .and. ;
  368.         oXbpRequestingHint:helpLink:isDerivedFrom("ToolHelpLabel"))
  369.         cSeek := oXbpRequestingHint:cargo
  370.         if oXbpRequestingHint:isDerivedFrom("XbpSLE") .and. !oXbpRequestingHint:editable
  371.             // Don't edit the tip (Automatic Data)
  372.         elseif oXbpRequestingHint:isDerivedFrom("XbpRadioButton")
  373.             //  
  374.         elseif oXbpRequestingHint:isDerivedFrom("XbpCheckBox")
  375.             //  
  376.         elseif cSeek <> NIL
  377.             nChild := 0
  378.             for i:=3 to 1 step -1
  379.                 nChild := val(right(cSeek,i))
  380.                 if nChild<>0
  381.                     i:=1
  382.                 endif
  383.             next i
  384.             select TIP
  385.             go top
  386.             DBSeek(cSeek,.F.)
  387.             if found() .and. alltrim(TIP->PROG_NAME) == cSeek
  388.                 cText := Trim( TIP->T_STRING + TIP->T_MEMO )
  389.                 rlock()
  390.             else
  391.                 go top
  392.                 locate for empty(PROG_NAME)
  393.                 if !found()
  394.                     append blank
  395.                 else
  396.                     rlock()
  397.                 endif
  398.                 replace PROG_NAME with cSeek
  399.             endif
  400.             cText := TipEdit(cText,nChild)
  401.         endif
  402.     ENDIF
  403.  
  404. RETURN
  405.  
  406.  
  407.  
  408. CLASS ToolHelpLabel
  409. ENDCLASS
  410.  
  411.  
  412.  
  413. /*
  414. * This function calculates the absolute position
  415. * from a given position relative to an XbasePART
  416. */
  417. STATIC FUNCTION calcAbsolutePosition(aPos,oXbp)
  418.     LOCAL aAbsPos := oXbp:CurrentPos()
  419.     LOCAL oParent := oXbp
  420.     LOCAL oDesktop := AppDesktop()
  421.  
  422.     if oParent:isDerivedFrom("XbpComboBox") .and. oParent:type <> XBPCOMBO_SIMPLE
  423.         aAbsPos[2] := aAbsPos[2]+(oParent:currentSize()[2]-20)
  424.     endif
  425.  
  426.     DO WHILE oParent <> oDesktop
  427.         oParent := oParent:setParent()
  428.         aAbsPos[1] += oParent:currentPos()[1]
  429.         aAbsPos[2] += oParent:currentPos()[2]
  430.     ENDDO
  431.  
  432. RETURN(aAbsPos)
  433.  
  434.  
  435.  
  436. FUNCTION TipEdit(cText,nChild)
  437.     LOCAL nEvent,mp1,mp2,oXbp
  438.     LOCAL oModal,oBtn,oLastFocus,oDa, lEdit := .T., aPos := {}, aSize
  439.     LOCAL cAlias := alias()
  440.  
  441.     aSize    := {376,190}
  442.     aPos     := AppDeskTop():currentSize()
  443.     aPos[1]  := (aPos[1]/2) - 188
  444.     aPos[2]  := (aPos[2]/2) - 95
  445.  
  446.     oModal := XbpDialog():new(AppDesktop(),SetAppWindow(),aPos,aSize)
  447.     oModal:sysmenu  := .F.
  448.     oModal:taskList := .F.
  449.     oModal:titleBar := .F.
  450.     oModal:keyBoard := {|nKey| iif(nKey == xbeK_ESC,PostAppEvent(xbeP_Close,,,oModal),nil)}
  451.     oModal:create()
  452.     oModal:drawingArea:setColorBG(GRA_CLR_BLACK)
  453.     oModal:setFontCompoundName("10.Alaska CRT")
  454.     oModal:setModalState(XBP_DISP_APPMODAL)
  455.     oDa := oModal:drawingarea
  456.  
  457.     oXbp    := XbpStatic():new()
  458.     oXbp:caption := "Enter the Tip for Item: "+TIP->PROG_NAME
  459.     oXbp:SetFontCompoundName("12.Arial Bold")
  460.     oXbp:options := XBPSTATIC_TEXT_VCENTER
  461.        oXbp:create( oDa, , {10,150}, {350,20}, {{XBP_PP_FGCLR,GRA_CLR_WHITE }} )
  462.  
  463.     oMLE := XbpMLE():new()
  464.     oMLE:editable := .T.
  465.     oMLE:datalink := { |x| IIf( x==NIL, cText, cText := x ) }
  466.     oMLE:SetColorBG( XBPSYSCLR_INFOBACKGROUND )
  467.     oMLE:SetFontCompoundName("10.Alaska CRT")
  468.     oMLE:clipSiblings := .T.
  469.     oMLE:tabstop := .T.
  470.     oMLE:HorizScroll := .F.
  471.     oMLE:create( oDa, , {10,40}, {350,100}, {{XBP_PP_INACTIVETEXT_FGCLR,GRA_CLR_BLACK }} )
  472.     oMLE:setdata()
  473.  
  474.     oBtn := XBPPushButton():new( oModal:drawingArea ,oModal:drawingArea, {280,10}, {80,20}, { { XBP_PP_COMPOUNDNAME, "10.Alaska CRT" } } )
  475.     oBtn:caption := "SAVE"
  476.     oBtn:clipSiblings    := .T.
  477.     oBtn:tabstop := .T.
  478.     oBtn:create()
  479.     oBtn:activate := { || PostAppEvent(xbeP_Close,,,oModal)}
  480.  
  481.     oBtn := XBPPushButton():new( oModal:drawingArea ,oModal:drawingArea, {180,10}, {80,20}, { { XBP_PP_COMPOUNDNAME, "10.Alaska CRT" } } )
  482.     oBtn:caption := "Cancel"
  483.     oBtn:clipSiblings    := .T.
  484.     oBtn:tabstop := .T.
  485.     oBtn:create()
  486.     oBtn:activate := { || PostAppEvent(xbeP_Close,,,oModal), lEdit := .F.}
  487.  
  488.     oModal:show()
  489.  
  490.     oLastFocus := SetAppFocus(oBtn)
  491.     SetAppFocus(oMLE)
  492.  
  493.     nEvent := 0
  494.     DO WHILE nEvent <> xbeP_Close
  495.         nEvent := AppEvent(@mp1,@mp2,@oXbp)
  496.         if nEvent == xbeP_Keyboard .and. mp1 == xbeK_ESC
  497.              lEdit := .F.
  498.             exit
  499.         elseif nEvent == xbeP_Keyboard .and. SetAppFocus()<>oMLE .and. mp1 == xbeK_RETURN
  500.             exit
  501.         endif
  502.         oXbp:handleEvent(nEvent,mp1,mp2)
  503.     ENDDO
  504.  
  505.     if lEdit
  506.         if rlock()
  507.             replace TIP->T_STRING with oMLE:editbuffer()
  508.             cTemp := substr(oMLE:editbuffer(),51)
  509.             if !empty(cTemp).or.!empty(TIP->T_MEMO)
  510.                 replace T_MEMO with trim(cTemp)
  511.             endif
  512.             unlock
  513.         endif
  514.     endif
  515.  
  516.     oModal:setModalState(XBP_DISP_MODELESS)
  517.     oModal:destroy()
  518.     SetAppFocus(oLastFocus)
  519.  
  520.  
  521. RETURN cText
  522.  
  523.  
  524.  
  525.  
  526. STATIC FUNCTION MakeHelp(cToolFileName)
  527.     LOCAL aStructure := { { "PROG_NAME"   , "C", 15, 0 },;
  528.                           { "T_STRING"    , "C", 50, 0 },;
  529.                           { "T_MEMO "     , "M", 10, 0 } }
  530.  
  531.     DbCreate( cToolFileName, aStructure, )
  532.  
  533. RETURN .T.
  534.  
  535.  
  536.  
  537. STATIC FUNCTION Wrap( cText )
  538.  
  539.     LOCAL aText := {}
  540.     LOCAL nSpace := 0, i
  541.     LOCAL cSearch := chr(13)+" ,.?;:+->)}|\/*"    // Possible Break characters
  542.  
  543.     do while len(cText)>nMaxLen
  544.         for i := 1 to len(cSearch)
  545.             nSpace    := rat(substr(cSearch,i,1),left(cText,nMaxLen))
  546.             if nSpace<>0
  547.                 i:=len(cSearch)
  548.             endif
  549.         next i
  550.         if nSpace==0
  551.             nSpace := nMaxLen    // Force a break if none of the characters in cSearch are found
  552.         endif
  553.         if substr(cText,nSpace,2)==chr(13)+chr(10)
  554.             nSpace := at(chr(13)+chr(10),left(cText,nMaxLen))
  555.             nSpace := nSpace-1
  556.         endif
  557.         if !empty(alltrim(left(cText,nSpace)))
  558.             aAdd(aText,strTran(left(cText,nSpace),chr(13)+chr(10)))
  559.         endif
  560.         cText := right(cText,len(cText)-nSpace)
  561.         if left(cText,2)==chr(13)+chr(10)
  562.             cText := right(cText,len(cText)-2)
  563.         endif
  564.         do while left(cText,2)==chr(13)+chr(10)
  565.             aAdd(aText," ")
  566.             cText := right(cText,len(cText)-2)
  567.         enddo
  568.     enddo
  569.     if !empty(cText)
  570.         aAdd(aText,strTran(cText,chr(13)+chr(10)))
  571.     endif
  572.  
  573. RETURN aText
  574.  
  575.  
  576.  
  577.  
  578.